import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

public class TypeChecker implements Kitsch.yyTree.Visitor {
    
    public boolean error;

    public static void main( String[] args ) {
        if ( args.length != 1 ) {
            System.err.println( "Usage: java Evaluator infile" );
            System.exit( 1 );
        }
        
        try {
            java.io.InputStream is = new java.io.FileInputStream( args[0] );
            Kitsch.yyInput scanner = new Kitsch.yyLex( is );
            
            java.lang.Object parser = scanner.getClass()
            .getEnclosingClass().newInstance();
            
            // arg[0]: if specified, toggle null storage (currently disabled)
            /*if (arg != null && arg.length > 0) {
                java.lang.reflect.Field yyAddNull = parser.getClass().getField("yyAddNull");
                yyAddNull.setBoolean(parser, yyAddNull.getBoolean(parser) ^ true);
            }*/
            
            // build tree
            java.lang.Object tree = parser.getClass()
                .getMethod("yyparse", new java.lang.Class[]{
                  Kitsch.yyInput.class, java.lang.Object[].class })
                .invoke(parser, scanner,
                  new java.lang.Object[]{ new Kitsch.yyTree() });
                  
            //evaluate the tree
            FuncVisitor fv = new FuncVisitor();
            Map<FuncVisitor.Function, Kitsch.yyTree.FuncDecl> funcsToNodes = 
                (Map)fv.visit( (Kitsch.yyTree.Prgm)tree );
            TypeChecker tc = new TypeChecker( funcsToNodes );
            tc.visit( (Kitsch.yyTree.Prgm)tree );
                  
        } catch ( java.io.FileNotFoundException ex ) {
            System.err.println( args[0] + ": File does not exist." );
            System.exit( 1 );
        } catch (java.lang.InstantiationException e) {
          System.err.println("cannot create parser ["+e+"]");
          System.exit(1);
        } catch (java.lang.IllegalAccessException e) {
          System.err.println("cannot create parser ["+e+"]");
          System.exit(1);
        } catch (java.lang.NoSuchMethodException e) {
          System.err.println("cannot create parser ["+e+"]");
          System.exit(1);
        } catch (java.lang.reflect.InvocationTargetException e) {
          System.err.println("parse error ["+e+"]");
          System.exit(1);
        }
    }

    private Map<String, Integer> symbols;
    private Map<String, Integer> globals;
    private Map<FuncVisitor.Function, Kitsch.yyTree.FuncDecl> funcsToNodes;
    private List<FuncVisitor.Function> checkedFuncs;
    private FuncVisitor.Function currentFunc;
    
    public TypeChecker( Map<FuncVisitor.Function, Kitsch.yyTree.FuncDecl> funcsToNodes) {
        symbols = new HashMap<String, Integer>();
        globals = new HashMap<String, Integer>();
        this.funcsToNodes = funcsToNodes;
        checkedFuncs = new ArrayList<FuncVisitor.Function>();
    }
    
    private FuncVisitor.Function lookupFunc( String fname ) {
        for ( FuncVisitor.Function f : funcsToNodes.keySet() ) {
            if ( f.name.equals( fname ) ) {
                return f;
            }
        }
        System.err.println( "Error: Function " + fname + " does not exist." );
        error = true;
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Prgm node) {
        Kitsch.yyTree.Visit elem;
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                elem.visit( this );
            }
        }
        
        if(this.error) {
            System.exit(1);
        }
        return globals;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.top_stmt node) {
        Kitsch.yyTree.Visit elem;
        //don't visit function decls, that'll be done at call time
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null && !( elem instanceof Kitsch.yyTree.FuncDecl ) ) {
                elem.visit( this );
            }
        }
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Stmt node) {
        Kitsch.yyTree.Visit elem;
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                elem.visit( this );
            }
        }
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.IfStmt node) {
        Kitsch.yyTree.Visit elem;
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                elem.visit( this );
            }
        }
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.IfBlock node) {
        Integer type = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( type != OperatorRegistry.BOOL ) {
            System.err.println( "Type error: non bool in conditional." );
            //System.exit( 1 );
            error = true;
        }
        for ( int x = 1; x < node.size(); x++ ) {
            ((Kitsch.yyTree.Visit)node.get(x)).visit( this );
        }
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.ElifBlock node) {
        Integer type = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( type != OperatorRegistry.BOOL ) {
            System.err.println( "Type error: non bool in conditional." );
            //System.exit( 1 );
            error = true;
        }
        for ( int x = 1; x < node.size(); x++ ) {
            ((Kitsch.yyTree.Visit)node.get(x)).visit( this );
        }
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.ElseBlock node) {
        for ( int x = 0; x < node.size(); x++ ) {
            ((Kitsch.yyTree.Visit)node.get(x)).visit( this );
        }
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.WhileStmt node) {
        Integer type = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( type != OperatorRegistry.BOOL ) {
            System.err.println( "Type error: non bool in conditional." );
            //System.exit( 1 );
            error = true;
        }
        for ( int x = 1; x < node.size(); x++ ) {
            ((Kitsch.yyTree.Visit)node.get(x)).visit( this );
        }
        return null;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.GlbDecl node ) {
        globals.put( (String)node.get(1), 0 );
        return null;   
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Assign node) {
        Map<String, Integer> correctMap = symbols;
        String name = (String)(((Kitsch.yyTree.IdCls)node.get(0)).get(0));
        if ( globals.get( name ) != null ) {
            //this is a global variable
            correctMap = globals;
        }
        //check array stuff (element 1)
        Integer rht = 0;
        if ( node.get(1) != null ) {
            //this is an attempt to index an array
            //visit the expr and make sure it's an int
            int mht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if ( mht != OperatorRegistry.INT ) {
                System.err.println( "Error: non-integer index in array element assignment" );
                error = true;
            }
            //get the type of the rhs
            rht = (Integer)((Kitsch.yyTree.Visit)node.get(2)).visit( this );
            int arrayType = OperatorRegistry.toArrayType( rht );
            //does the name already exist?  if so, it's illegal to change type
            if ( correctMap.get( name ) != null ) {
                if ( correctMap.get( name ) != arrayType ) {
                    System.err.println( "Error: changing type of an array element" );
                    error = true;
                }
            } else {
                correctMap.put( name, arrayType );
            }
            //note that this will write the array type - is this the Right Thing?
            Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        } else {
            rht = (Integer)((Kitsch.yyTree.Visit)node.get(2)).visit( this );
            //legal to change types in assignment, but we've gotta update symbols
            correctMap.put( name, rht );
            //visit the lhs so that the new type gets written
            Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        }
        
        node.setType( rht );
        
        return rht;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.FuncDecl node ) {
        //the first thing is a string with the function's name
        FuncVisitor.Function f = lookupFunc( (String)node.get(0) );
        //since function decls are visited at "call-time", we might have visited
        //this already - if we have, the return type will be set
        if ( checkedFuncs.contains(f) && OperatorRegistry.isValidType(f.returnType) ) {
            // We've already seen the function before and we know the correct return type
            return f.returnType;
        } else {
            // This happens only if a function is recursive - 
            //resolve it here by looking ahead at the return statement
            
            //the last thing is the return statement, which might be null
            //in which case we have a void func, otherwise this type is the func's type
            Kitsch.yyTree.Visit last = (Kitsch.yyTree.Visit)node.get( node.size() - 1 );
            int type;
            if ( last == null ) {
                type = OperatorRegistry.VOID;
                f.returnType = type;
                node.setType( type );
            } else {
                // Look at the TypeCls part of the return statement
                // We can't simply visit the whole RetCls object because the 
                // expression might contain things that haven't been type 
                // checked in the rest of the function body 
                type = (Integer)((Kitsch.yyTree.Visit)((Kitsch.yyTree.RetCls)last).get(0))
                    .visit( this );
                f.returnType = type;
                node.setType( type );
            }
        }
        
        checkedFuncs.add( f );
        currentFunc = f;
        //cheap way to do scope, need to change if we allow globals
        Map<String, Integer> oldSymbols = symbols;
        symbols = new HashMap<String, Integer>();
        //the next thing is the parameter list, so visit them
        ((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        //then we've got the body, so visit that normally
        Kitsch.yyTree.Visit elem;
        for ( int x = 2; x < node.size()-1; x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                elem.visit( this );
            }
        }
        
        //the last thing is the return statement, which might be null
        //in which case we have a void func, otherwise this type is the func's type
        Kitsch.yyTree.Visit last = (Kitsch.yyTree.Visit)node.get( node.size() - 1 );
        int type;
        if ( last == null ) {
            type = OperatorRegistry.VOID;
            f.returnType = type;
            node.setType( type );
        } else {
            type = (Integer)last.visit( this );
            f.returnType = type;
            node.setType( type );
        }
        
        
        //restore old "scope"
        symbols = oldSymbols;
        return type;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.Params node ) {
        //the parameters without types should have default values, or else this function
        //was called with too few parameters
        Kitsch.yyTree.Visit elem;
        int paramsIndex = 0;
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                if ( elem instanceof Kitsch.yyTree.IdCls ) {
                    //add this parameter to the symbol table
                    String name = (String)
                        (((Kitsch.yyTree.IdCls)elem).get(0));
                    FuncVisitor.Parameter p = currentFunc.lookupParameter( name );
                    if ( p == null ) {
                        System.err.println( "Bug: Function " + currentFunc.name + 
                            " has no parameter " + p.name );
                        error = true;
                    } else if ( p.type == 0 ) {
                        System.err.println( "Error: Too few parameters passed to function "
                            + currentFunc.name );
                        //this is unrecoverable
                        System.exit( 1 );
                    } else {
                        symbols.put( p.name, p.type );
                        elem.visit( this );
                    }
                } else {
                    //assignment, check and make sure the types match
                    int type = (Integer)elem.visit( this );
                    String name = (String)((Kitsch.yyTree.IdCls)
                        ((Kitsch.yyTree.DefPar)elem).get(0)).get(0);
                    FuncVisitor.Parameter p = currentFunc.lookupParameter( name );
                    if ( p.type == 0 ) {
                        p.type = type;
                    } else if ( p.type != type ) {
                        System.err.println( "Error: In function " + currentFunc.name +
                            ", parameter " + p.name + " has a default type of " + 
                            OperatorRegistry.typeToString( type ) + " and was called with type " 
                            + OperatorRegistry.typeToString( p.type ) );
                        error = true;
                    }
                }
                paramsIndex++;
            }
        }
        
        return null;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.DefPar node ) {
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        //legal to change types in assignment, but we've gotta update symbols
        String name = (String)(((Kitsch.yyTree.IdCls)node.get(0)).get(0));
        symbols.put( name, rht );
        //visit the lhs so that the new type gets written
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        
        node.setType( rht );
        
        return rht;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.RetCls node ) {
        //the 1st child is the return type
        int type = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit(this);
        //the 2nd child should be an expr, visit it to type those nodes
        ((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        node.setType(type);
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Print node) {
        Integer type = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Read node) {
        node.setType( OperatorRegistry.STRING );
        String name = (String)(((Kitsch.yyTree.IdCls)node.get(0)).get(0));
        symbols.put( name, OperatorRegistry.STRING );
        //visit the ident so that the new type gets written
        Integer type = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        return OperatorRegistry.STRING;
    }
    
    private int checkBinaryOp( Kitsch.yyTree.Visit node, int lht, int rht, int op ) {
        if ( lht != rht ) {
            System.err.println( "Incompatible types in " + OperatorRegistry.opToString( op ) );
            System.err.println( OperatorRegistry.typeToString( lht ) + " and " + 
                OperatorRegistry.typeToString( rht ) );
            //System.exit( 1 );
            error = true;
        }
        if ( !OperatorRegistry.isSupported( lht, op ) ) {
            System.err.println( "Unsupported type for " + OperatorRegistry.opToString( op )
                 + ": " + OperatorRegistry.typeToString( lht ) );
             //System.exit( 1 );
             error = true;
        }
        node.setType( lht );
        return lht;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Lt node ) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.LT );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Lte node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.LTE );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    public java.lang.Object visit ( Kitsch.yyTree.Gt node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.GT );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    public java.lang.Object visit ( Kitsch.yyTree.Gte node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.GTE );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }    
    public java.lang.Object visit ( Kitsch.yyTree.Eq node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.EQ );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    public java.lang.Object visit ( Kitsch.yyTree.Neq node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.NEQ );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    public java.lang.Object visit ( Kitsch.yyTree.LgAnd node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.LAND );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    public java.lang.Object visit ( Kitsch.yyTree.LgOr node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        checkBinaryOp( node, lht, rht, OperatorRegistry.LOR );
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    public java.lang.Object visit ( Kitsch.yyTree.LgNot node) {
        Integer oht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( !OperatorRegistry.isSupported( oht, OperatorRegistry.LNOT ) ) {
            System.err.println( "Unsupported type for " + 
                OperatorRegistry.opToString( OperatorRegistry.LNOT ) + ": " + 
                OperatorRegistry.typeToString( oht ) );
            //System.exit( 1 );
            error = true;
        }
        node.setType( OperatorRegistry.BOOL );
        return OperatorRegistry.BOOL;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Expr node) {
        Integer type = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit(this);
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Add node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.ADD );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Sub node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.SUB );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Mul node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        if ( !OperatorRegistry.isSupported( lht, OperatorRegistry.MUL ) ) {
            System.err.println( "Unsupported type for " + 
                OperatorRegistry.opToString( OperatorRegistry.MUL )
                 + ": " + OperatorRegistry.typeToString( lht ) );
             //System.exit( 1 );
             error = true;
        }
        if ( !OperatorRegistry.isSupported( rht, OperatorRegistry.MUL ) ) {
            System.err.println( "Unsupported type for " + 
                OperatorRegistry.opToString( OperatorRegistry.MUL )
                 + ": " + OperatorRegistry.typeToString( rht ) );
             //System.exit( 1 );
             error = true;
        }
        //hack - can't multiply two strings
        if ( lht == rht && lht == OperatorRegistry.STRING ) {
            System.err.println( "Incompatible types in " + 
                OperatorRegistry.opToString( OperatorRegistry.MUL ) );
            System.err.println( OperatorRegistry.typeToString( lht ) + " and " 
                + OperatorRegistry.typeToString( rht ) );
            //System.exit( 1 );
            error = true;
        }
        node.setType( lht );
        return lht;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Div node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.DIV );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Mod node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.MOD );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Exp node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.EXP );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.BwAnd node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.BAND );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.BwOr node)  {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.BOR );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.BwXor node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        int type = checkBinaryOp( node, lht, rht, OperatorRegistry.BXOR );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.BwNot node) {
        Integer oht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( !OperatorRegistry.isSupported( oht, OperatorRegistry.BNOT ) ) {
            System.err.println( "Unsupported type for " + 
                OperatorRegistry.opToString( OperatorRegistry.BNOT ) + ": " + 
                OperatorRegistry.typeToString( oht ) );
            //System.exit( 1 );
            error = true;
        }
        node.setType( oht );
        return oht;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.Call node ) {
        //the first thing is a string which is the function name
        FuncVisitor.Function f = lookupFunc( (String)node.get( 0 ) );
        if ( f.name.equals( "len" ) ) {
            //there should be one parameter, which is an array type
            if ( node.get(1) == null || node.size() != 3 ) {
                System.err.println( "Incorrect number of parameters for function len(array)" );
                error = true;
                return 0;
            }
            int atype = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if ( !OperatorRegistry.isArrayType( atype ) ) {
                System.err.println( "Wrong type for function len(array): not an array" );
                error = true;
                return 0;
            }
            node.setType( OperatorRegistry.INT );
            return OperatorRegistry.INT;
        }
        //the rest are the function's parameters
        int paramIndex = 0;
        Kitsch.yyTree.Visit elem;
        for ( int x = 1; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                int type = (Integer)elem.visit( this );
                if ( paramIndex >= f.params.size() ) {
                    System.err.println( "Too many parameters given to " + f.name );
                    error = true;
                    break;
                } else {
                    if ( f.params.get( paramIndex ).type != 0 ) {
                        if ( type != f.params.get( paramIndex ).type ) {
                            System.err.println( "Incorrect parameter type for function "
                                                + f.name + " for parameter " + paramIndex );
                            error = true;
                        }
                        paramIndex++;
                    } else {
                        FuncVisitor.Parameter p = f.params.get( paramIndex );
                        p.type = type;
                        paramIndex++;
                    }
                }
            }
        }
        
        //anyway, now that the parameters are typed it's time to type check the function
        int ftype = (Integer)funcsToNodes.get( f ).visit( this );
        node.setType( ftype );
        return ftype;
    }
    
    //not implemented yet
    public java.lang.Object visit( Kitsch.yyTree.ArrayLit node ) {
        //visit each element, make sure they're the same type
        if ( node.size() < 1 ) {
            return OperatorRegistry.VOID_ARRAY;
        }
        Kitsch.yyTree.Visit elem = (Kitsch.yyTree.Visit)node.get( 0 );
        int type = (Integer)elem.visit( this );
        for ( int x = 1; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                int etype = (Integer)elem.visit( this );
                if ( etype != type ) {
                    System.err.println( "Nonhomogenous array literal: " + 
                        OperatorRegistry.typeToString( type ) + " and " +
                        OperatorRegistry.typeToString( etype ) );
                    error = true;
                    break;
                }
            }
        }
        //convert the type into an array type
        int atype = OperatorRegistry.toArrayType( type );
        node.setType( atype );
        return atype;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.Parens node) {
        Integer inner = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        node.setType( inner );
        return inner;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.Cast node ) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        node.setType( lht );
        return lht;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.UNeg node) {
        Integer inner = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( !OperatorRegistry.isSupported( inner, OperatorRegistry.UNEG ) ) {
            System.err.println( "Unsupported type for " + 
                OperatorRegistry.opToString( OperatorRegistry.UNEG ) + ": " + 
                OperatorRegistry.typeToString( inner ) );
            //System.exit( 1 );
            error = true;
        }
        node.setType( inner );
        return inner;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.UPos node) {
        Integer inner = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( !OperatorRegistry.isSupported( inner, OperatorRegistry.UPOS ) ) {
            System.err.println( "Unsupported type for UPOS: " + 
                OperatorRegistry.typeToString( inner ) );
            //System.exit( 1 );
            error = true;
        }
        node.setType( inner );
        return inner;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.NumCls node) {
        return OperatorRegistry.INT;
    }
    
    public java.lang.Object visit (Kitsch.yyTree.BoolCls node) {
        return OperatorRegistry.BOOL;
    }
    
    public java.lang.Object visit (Kitsch.yyTree.StrCls node) {
        return OperatorRegistry.STRING;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.TypeCls node ) {
        String type = (String)node.get( 0 );
        int actual = 0;
        if ( type.equals( "int" ) ) {
            actual = OperatorRegistry.INT;
        } else if ( type.equals( "string" ) ) {
            actual = OperatorRegistry.STRING;
        } else if ( type.equals( "bool" ) ) {
            actual = OperatorRegistry.BOOL;
        } else if ( type.equals( "int[]" ) ) {
            actual = OperatorRegistry.INT_ARRAY;
        } else if ( type.equals( "string[]" ) ) {
            actual = OperatorRegistry.STR_ARRAY;
        } else if ( type.equals( "bool[]" ) ) {
            actual = OperatorRegistry.BOOL_ARRAY;
        }
        node.setType( actual );
        return actual;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.IdCls node) {
        String ident = (String)node.get( 0 );
        Map<String, Integer> correctMap = symbols;
        if ( symbols.get( ident ) == null ) {
            correctMap = globals;
        }
        if ( correctMap.get( ident ) == null ) {
            System.err.println( "Fatal: " + ident + " is not in scope." );
            error = true;
        }
        Integer type = correctMap.get( ident );
        node.setType( type );
        return type;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.EolCls node ) {
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.SubStr node) {
        Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( !OperatorRegistry.isSupported( lht, OperatorRegistry.INDX ) ) {
            System.err.println( "Unsupported type for index: " + 
                OperatorRegistry.typeToString( lht ) );
            //System.exit( 1 );
            error = true;
        }
        ((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        //check for arrays - if so, dereference to the base type
        if ( OperatorRegistry.isArrayType( lht ) ) {
            lht = OperatorRegistry.toNonArrayType( lht );
        }
        node.setType( lht );
        return lht;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.Range node ) {
        if ( node.get( 0 ) != null ) {
            Integer lht = (Integer)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
            if ( !OperatorRegistry.isSupported( lht, OperatorRegistry.RNG ) ) {
                System.err.println( "Unsupported type for range: " + 
                    OperatorRegistry.typeToString( lht ) );
                //System.exit( 1 );
            error = true;
            }
        }
        if ( node.get( 1 ) != null ) {
            Integer rht = (Integer)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if ( !OperatorRegistry.isSupported( rht, OperatorRegistry.RNG ) ) {
                System.err.println( "Unsupported type for range: " + 
                    OperatorRegistry.typeToString( rht ) );
                //System.exit( 1 );
            error = true;
            }
        }
        return null;
    }
    
    /** hook for unknown classes. */
    public java.lang.Object visit (Kitsch.yyTree.Visit node) {
        return null;
    }
}
